package com.lleice.web.aspect;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.lleice.common.annotation.RequestLog;
import com.lleice.common.entity.common.SaveRequestLog;
import com.lleice.common.strategy.MessageStrategy;
import com.lleice.service.entity.sys.SysRequestLog;
import com.lleice.service.service.sys.ISysRequestLogService;
import com.lleice.web.constant.ScanPackageConstant;
import com.lleice.web.message.kafka.topic.TopicConstant;
import com.google.gson.Gson;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 访问日志打印(如没有入库的实现类,则不加载)
 * @author lizihao
 *
 */
@Component
@Aspect
@Order(1)
@Slf4j
public class RequestLogAspect {
	/**
	 * 若要更换其他mq 生产者只需实现MessageStrategy的saveRequestLog接口即可,只需要在生产者实现类加上@Primary即可(同时注掉原来的@Primary)
	 */
	@Autowired
    private MessageStrategy producer;
	
	/**
	 * 请求日志是否保存出参
	 */
	@Value("${logging.isSaveReturn}")
	private boolean isSaveReturn;
	
	private final static String IS_TEST_FLAG = "true";
    
    private static final ThreadLocal<Long> START_TIME_MILLIS = new ThreadLocal<>();
    
	@Pointcut(ScanPackageConstant.REQUEST_LOG_POINTCUT)
    public void requestLog() {
    }

	
    /**
     * 环绕(在目标方法前后都执行一段逻辑)
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("requestLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    	String requestArgs = new Gson().toJson(proceedingJoinPoint.getArgs());
    	log.info("请求参数:"+requestArgs);
    	START_TIME_MILLIS.set(System.currentTimeMillis());
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取 @RequestLog 注解的描述信息
        String methodDescription = getAspectLogDescription(proceedingJoinPoint);
        //如果不需要统计返回时间和收集返回信息,直接用@before即可 proceedingJoinPoint.proceed()作用是调用目标方法,如果不调用 前面相当于@before
        Object result = proceedingJoinPoint.proceed();
        //单元测试不打印日志
        String isTest = request.getHeader("isTest");
        if(StrUtil.isBlank(isTest) || !IS_TEST_FLAG.equals(isTest)) {
        	SysRequestLog requestLog = new SysRequestLog()
            		.setRequestUrl(request.getRequestURL().toString())
            		.setRequestDescription(methodDescription)
            		.setRequestMethod(request.getMethod())
            		.setClassMethod(proceedingJoinPoint.getSignature().getDeclaringTypeName()+"."+proceedingJoinPoint.getSignature().getName())
            		.setRequestIp(request.getRemoteAddr())
            		.setRequestArgs(requestArgs)
            		.setRequestResult(isSaveReturn?new Gson().toJson(result):null)
            		.setTimeConsuming((System.currentTimeMillis() - START_TIME_MILLIS.get())+"ms");
        		 producer.saveRequestLog(new SaveRequestLog().setTopic(TopicConstant.REQUEST_LOG_TOPIC).setObj(requestLog));
            START_TIME_MILLIS.remove();
        }
        return result;
    }
    
    /**
     * 获取切面注解的描述
     *
     * @param joinPoint 切点
     * @return 描述信息
     * @throws Exception
     */
    public String getAspectLogDescription(JoinPoint joinPoint){
    	MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        RequestLog annotation = signature.getMethod().getAnnotation(RequestLog.class);
        return annotation.description();
    }
    
    /**
     * 默认的请求日志入库方式
     * @author lizihao
     *
     */
    @Service
    class DbProducer implements MessageStrategy{

    	@Autowired
    	private ISysRequestLogService iSysRequestLogService;
    	
		@Override
		public void saveRequestLog(SaveRequestLog saveRequestLog) {
			iSysRequestLogService.save((SysRequestLog) saveRequestLog.getObj());
		}
    	
    }
}
